#include "preHeader.h"
#include "InstanceMgr.h"
#include "WorldHook.h"
#include "StoryHook.h"
#include "DungeonHook.h"
#include "ServerMaster.h"

InstanceMgr::InstanceMgr()
: m_instSeed(0)
{
}

InstanceMgr::~InstanceMgr()
{
	for (auto& pair : m_gameMaps) {
		delete pair.second;
	}
}

bool InstanceMgr::LoadGameMaps()
{
	auto pMapTbl = sDBMgr.GetTable<MapInfo>();
	for (auto itr = pMapTbl->Begin(); itr != pMapTbl->End(); ++itr) {
		const MapInfo* pMapInfo = &itr->second;
		auto pGameMap = new GameMap(pMapInfo);
		pGameMap->LoadDBRes();
		m_gameMaps.emplace(pGameMap->GetMapId(), pGameMap);
	}
	return true;
}

const GameMap* InstanceMgr::GetGameMap(uint32 mapId) const
{
	auto itr = m_gameMaps.find(mapId);
	return itr != m_gameMaps.end() ? itr->second : NULL;
}

bool InstanceMgr::Prepare()
{
	const auto& cfg = IServerMaster::GetInstance().GetConfig();
	const size_t n = cfg.GetInteger("OTHER", "INSTANCE_THREAD", 1);
	for (size_t i = 0; i < 1 || i < n; ++i) {
		PushThread(new MapInstanceWorker);
	}
	return true;
}

MapInstanceWorker* InstanceMgr::GetLoadWorkerLowest() const
{
	uint32 sltLoadValue = 0;
	MapInstanceWorker* sltWorker = NULL;
	ThreadPool::Foreach([&](Thread* pThread) {
		auto pWorker = static_cast<MapInstanceWorker*>(pThread);
		auto loadValue = pWorker->GetLoadValue();
		if (sltWorker == NULL || sltLoadValue > loadValue) {
			sltWorker = pWorker, sltLoadValue = loadValue;
		}
	});
	return sltWorker;
}

IMapHook* InstanceMgr::CreateMapHook(MapInstance* pMapInstance) const
{
	switch (MapInfo::Type(pMapInstance->getMapType())) {
	case MapInfo::Type::WorldMap:
		return new WorldHook(pMapInstance);
	case MapInfo::Type::Story:
		return new StoryHook(pMapInstance);
	case MapInfo::Type::Dungeon:
		return new DungeonHook(pMapInstance);
	default:
		assert(false && "can't reach here.");
		return NULL;
	}
}

MapInstance* InstanceMgr::CreateAndGetInstance(
	uint32 mapType, uint32 mapId, uint32 instUid, ObjGUID instOwner)
{
	if (instOwner != ObjGUID_NULL) {
		auto pMapInstance = GetMapInstance(mapType, mapId, instOwner);
		if (pMapInstance != NULL) {
			return pMapInstance;
		}
	} else if (instUid != INST_UID_INVALID) {
		auto instGuid = MakeInstGuid(mapType, mapId, instUid);
		auto pMapInstance = GetMapInstance(instGuid);
		if (pMapInstance != NULL) {
			return pMapInstance;
		}
	}
	if (instUid != INST_UID_INVALID) {
		if (CanAutoCreateMapInstance(mapType, mapId)) {
			return CreateInstance(mapType, mapId, instUid, instOwner);
		}
	} else {
		if (CanPriorCreateMapInstance(mapType, mapId)) {
			return CreateInstance(mapType, mapId, instUid, instOwner);
		} else {
			return GetAppropriateMapInstance(mapType, mapId);
		}
	}
	return NULL;
}

MapInstance* InstanceMgr::CreateInstance(
	uint32 mapType, uint32 mapId, uint32 instUid, ObjGUID instOwner)
{
	const GameMap* pGameMap = GetGameMap(mapId);
	if (pGameMap == NULL) {
		WLOG("Create a invalid map instance [%u]", mapId);
		return NULL;
	}

	InstGUID instGuid;
	instGuid.TID = mapType;
	instGuid.MAPID = mapId;
	if (instUid != INST_UID_INVALID) {
		instGuid.UID = instUid;
	} else {
		instGuid.UID = ++m_instSeed;
	}

	auto pMapInstanceWorker = GetLoadWorkerLowest();
	auto pMapInstance = new MapInstance(*pGameMap, instGuid, instOwner);
	DLOG("MapInstance [TYPE ID: %hu, MAP ID: %hu, INST ID: %u] Created.",
		instGuid.TID, instGuid.MAPID, instGuid.UID);

	auto pHook = CreateMapHook(pMapInstance);
	pMapInstance->SetHook(pHook);

	do {
		std::lock_guard<std::shared_mutex> lock(m_mapSharedMutex);
		m_mapInstances.emplace(instGuid, pMapInstance);
		if (instOwner != ObjGUID_NULL) {
			m_mapOwners.emplace(instOwner, pMapInstance);
		}
	} while (0);

	pMapInstanceWorker->AddMapInstance(pMapInstance);
	pMapInstance->Init();

	return pMapInstance;
}

void InstanceMgr::RemoveInstance(MapInstance* pMapInstance)
{
	const InstGUID instGuid = pMapInstance->getInstGuid();
	const ObjGUID instOwner = pMapInstance->getOwnerGuid();
	DLOG("MapInstance [TYPE ID: %hu, MAP ID: %hu, INST ID: %u] Removed.",
		instGuid.TID, instGuid.MAPID, instGuid.UID);

	do {
		std::lock_guard<std::shared_mutex> lock(m_mapSharedMutex);
		m_mapInstances.erase(instGuid);
		if (instOwner != ObjGUID_NULL) {
			auto pair = m_mapOwners.equal_range(instOwner);
			auto itr = std::find_if(pair.first, pair.second,
				[=](const std::pair<ObjGUID, MapInstance*>& pair) {
					return pair.second == pMapInstance;
			});
			if (itr != pair.second) {
				m_mapOwners.erase(itr);
			}
		}
	} while (0);

	delete pMapInstance;
}

MapInstance* InstanceMgr::GetMapInstance(InstGUID instGuid) const
{
	std::shared_lock<std::shared_mutex> lock(m_mapSharedMutex);
	auto itr = m_mapInstances.find(instGuid);
	if (itr != m_mapInstances.end()) {
		auto pMapInstance = itr->second;
		if (!pMapInstance->IsShutdown()) {
			return pMapInstance;
		}
	}
	return NULL;
}

MapInstance* InstanceMgr::GetMapInstance(
	uint32 mapType, uint32 mapId, ObjGUID instOwner) const
{
	std::shared_lock<std::shared_mutex> lock(m_mapSharedMutex);
	auto pair = m_mapOwners.equal_range(instOwner);
	for (auto itr = pair.first; itr != pair.second; ++itr) {
		auto pMapInstance = itr->second;
		auto instGuid = pMapInstance->getInstGuid();
		if (instGuid.TID == mapType && instGuid.MAPID == mapId) {
			if (!pMapInstance->IsShutdown()) {
				return pMapInstance;
			}
		}
	}
	return NULL;
}

bool InstanceMgr::CanAutoCreateMapInstance(uint32 mapType, uint32 mapId)
{
	switch (MapInfo::Type(mapType)) {
	case MapInfo::Type::WorldMap:
		return true;
	default:
		return false;
	}
}

bool InstanceMgr::CanPriorCreateMapInstance(uint32 mapType, uint32 mapId)
{
	switch (MapInfo::Type(mapType)) {
	case MapInfo::Type::WorldMap:
		return false;
	default:
		return true;
	}
}

MapInstance* InstanceMgr::GetAppropriateMapInstance(uint32 mapType, uint32 mapId)
{
	switch (MapInfo::Type(mapType)) {
	case MapInfo::Type::WorldMap:
		return GetAppropriateMapInstance4WorldMap(mapId);
	default:
		assert(false && "can't reach here.");
		return NULL;
	}
}

MapInstance* InstanceMgr::GetAppropriateMapInstance4WorldMap(uint32 mapId)
{
	return GetMapInstance(MakeInstGuid((u32)MapInfo::Type::WorldMap, mapId, 0));
}

void InstanceMgr::ForeachAllInstance(const std::function<void(MapInstance*)>& func) const
{
	std::shared_lock<std::shared_mutex> lock(m_mapSharedMutex);
	for (const auto& pair : m_mapInstances) {
		auto pMapInstance = pair.second;
		pMapInstance->AddEventSafe([=]() {
			func(pMapInstance);
		});
	}
}

void InstanceMgr::ForeachAllPlayer(const std::function<void(Player*)>& func) const
{
	std::shared_lock<std::shared_mutex> lock(m_mapSharedMutex);
	for (const auto& pair : m_mapInstances) {
		auto pMapInstance = pair.second;
		pMapInstance->AddEventSafe([=]() {
			for (const auto& pair : pMapInstance->GetPlayerStorageMap()) {
				func(pair.second);
			}
		});
	}
}
